<?php

require_once('logger.php');
require_once('packet.php');
require_once('jujucore.php');
require_once('cache_packet.php');

class    CacheClient
{
    public  $service;
    private $server_addresses;
    private $channels;
    private $logger;
        
    public function __construct($service, $server_addresses)
    {
        $this->service = $service;
        $this->server_addresses = $server_addresses;
     
        $this->logger = getLogger();
           
        $this->channels = array();
        foreach ($server_addresses as $server_address) {
            try {
                $channel = $this->makeChannel($server_address);
            }
            catch(Exception $ex) {
                $channel = NULL;
            }
            $this->channels[] = $channel;
        }
    }
    
    public function __destruct()
    {
        foreach ($this->channels as $channel) {
            if (!empty($channel)) {
                socket_close($channel->socket);
                unset($channel);
            }
        }
    }
    
    public function makeChannel($server_address)
    {
        list($ip, $port) = $server_address;
        $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        $ok = socket_connect($sock, $ip, $port);
        if (!$ok) {
            socket_close($sock);

            $errorstr = sprintf("Error connect socket to (%s:%s) -- [%d] %s", $ip, $port, socket_last_error(), socket_strerror(socket_last_error()));
            $this->logger->error($errorstr);
            
            throw new RuntimeError($errorstr);
        }
        
        $channel = new JuTCPChannel($sock);
        return $channel;
    }
    
    public function getChannel($key) 
    {
        $index = 0;
        
        if (count($this->channels) > 1) {
            throw new NotImplementedErrror();
        }
         
         $channel = $this->channels[$index];
         if (empty($channel)) {
             $address = $this->server_addresses[$index];
             $channel = $this->makeChannel($address);
             $this->channels[$index] = $channel;
         }   
         
         return    $channel;
    }
    
    public function insert($key, $data, $timeout=0)
    {
        $cache_packet = new CachePacket();
        $cache_packet->setRequest();
        $cache_packet->setInsertAction();
        $cache_packet->key = $key;
        $cache_packet->data = $data;
        $cache_packet->timeout = $timeout;
        $body = $cache_packet->encodeRequest();
        
        $pkt = new Packet();
        $pkt->service = $this->service;
        $pkt->body = $body;
        $pkt->setRequest();
        list($request, $packid) = $pkt->enpack();
        
        $channel = $this->getChannel($key);
        $channel->sendRecord($request . "\n");
        
        $response = '';
        $response_length = $channel->recvRecord($response);
        if (empty($response)) {
            $errorstr = sprintf('Fail to response to INSERT(key=%s data=%s timeout=%d)', $key, $data, $timeout);
            $this->logger->error($errorstr);
            throw new RuntimeError($errorstr);
        }
        
        $pkt->body = '';
        $pkt->depack($response);
        $body = $pkt->body;
        $cache_packet->decodeResponse($body);
        
        return $cache_packet->status;
    }    // insert() 
    
    public function erase($key)
    {
        $cache_packet = new CachePacket();
        $cache_packet->setRequest();
        $cache_packet->setEraseAction();
        $cache_packet->key = $key;
        $body = $cache_packet->encodeRequest();
        
        $pkt = new Packet();
        $pkt->service = $this->service;
        $pkt->body = $body;
        $pkt->setRequest();
        list($request, $packid) = $pkt->enpack();
        
        $channel = $this->getChannel($key);
        $channel->sendRecord($request . "\n");
        
        $response = '';
        $response_length = $channel->recvRecord($response);
        if (empty($response)) {
            $errorstr = sprintf('Fail to response to ERASE(key=%s)', $key);
            $this->logger->error($errorstr);
            throw new RuntimeError($errorstr);
        }
        
        $pkt->body = '';
        $pkt->depack($response);
        $body = $pkt->body;
        $cache_packet->decodeResponse($body);
        
        return $cache_packet->status;
    }    // erase() 
    
    public function lookup($key)
    {
        $cache_packet = new CachePacket();
        $cache_packet->setRequest();
        $cache_packet->setLookupAction();
        $cache_packet->key = $key;
        $body = $cache_packet->encodeRequest();
        
        $pkt = new Packet();
        $pkt->service = $this->service;
        $pkt->body = $body;
        $pkt->setRequest();
        list($request, $packid) = $pkt->enpack();
        
        $channel = $this->getChannel($key);
        $channel->sendRecord($request . "\n");
        
        $response = '';
        $response_length = $channel->recvRecord($response);
        if (empty($response)) {
            $errorstr = sprintf('Fail to response to LOOKUP(key=%s)', $key);
            $this->logger->error($errorstr);
            throw new RuntimeError($errorstr);
        }
        
        $pkt->body = '';
        $pkt->depack($response);
        $body = $pkt->body;
        $cache_packet->decodeResponse($body);
        
        return array($cache_packet->status, $cache_packet->data);
    }    // erase() 
    
}    // class CacheClient
